#import "CocoaType.h"
#import "CocoaGlobals.h"
#import "CocoaClass.h"
#import "CocoaLink.h"
#import <objc/objc-class.h>

@implementation CocoaType

- (id) initWithType: (const char*) type
{
	self = [super init];
	_type = type;
	if(!type || !*type)
	{
		[self dealloc];
		return nil;
	}
	return self;
}

+ (id) typeWithType: (const char*) type
{
	return [[[CocoaType alloc] initWithType: type] autorelease];
}

- (void) dealloc
{
	[super dealloc];
}

- (NSString*) description
{
	const char* type = _type;
	NSString* prefix = @"";
	NSString* name;
	
	if(*type == 'r')
	{
		prefix = @"Const ";
		type++;
	}
	switch(*type)
	{
		case _C_ID:
			name = @"CocoaObject (instance)";
			break;
		case _C_CLASS:
			name = @"CocoaObject (class)";
			break;
		case _C_SEL:
			name = @"Selector";
			break;
		case _C_CHR:
			name = @"Character";
			break;
		case _C_UCHR:
			name = @"Unsigned Character";
			break;
		case _C_SHT:
			name = @"Short Integer";
			break;
		case _C_USHT:
			name = @"Unsigned Short Integer";
			break;
		case _C_INT:
			name = @"Integer";
			break;
		case _C_UINT:
			name = @"Unsigned Integer";
			break;
		case _C_LNG:
			name = @"Long Integer";
			break;
		case _C_ULNG:
			name = @"Unsigned Long Integer";
			break;
		case _C_FLT:
			name = @"Single Precision Decimal Number";
			break;
		case _C_DBL:
			name = @"Double Precision Decimal Number";
			break;
		case _C_BFLD:
			name = @"???";
			break;
		case _C_VOID:
			name = @"Void";
			break;
		case _C_UNDEF:
			name = @"Undefined";
			break;
		case _C_PTR:
		{
			CocoaType* t = [CocoaType typeWithType: type + 1];
			if(t)
				name = [[t description] stringByAppendingString: @" Pointer"];
			else
				name = @"Pointer";
			break;
		}	
		case _C_CHARPTR:
			name = @"Character Pointer";
			break;
		case _C_ARY_B:
			name = @"Array";//ROB_FIXME
			break;
		case _C_ARY_E:
		case _C_UNION_B:
		case _C_UNION_E:
			name = @"";//ROB_FIXME
			break;
		case _C_STRUCT_B:
		{
			const char* eq = strchr(type, '=');
			if(eq)
			{
				NSMutableString* str = [NSMutableString stringWithCapacity: 0];
				NSString* name = [NSString stringWithCString: type+1 length: (eq-(type+1))];
				CocoaType* next = [CocoaType typeWithType: eq+1];
				
				[str appendFormat: @"Struct %@ { ", name];
				while(next && (*next->_type != _C_STRUCT_E))
				{
					[str appendFormat: @"%@, ", next];
					next = [next nextType];
				}
				[str appendString: @"}"];
				name = str;
			}
			else
				name = @"Struct";
			break;
		}
		case _C_STRUCT_E:
		default:
			name = @"Unknown";
			break;
	}
	return [prefix stringByAppendingString: name];
}

- (CocoaType*) nextType
{
	const char* next = NULL;
	const char* type = _type;
	
	if(*type == 'r')
		type++;
	switch(*type)
	{
		case _C_ID:
			next = type + 1;
			break;
		case _C_CLASS:
			next = type + 1;
			break;
		case _C_SEL:
			next = type + 1;
			break;
		case _C_CHR:
			next = type + 1;
			break;
		case _C_UCHR:
			next = type + 1;
			break;
		case _C_SHT:
			next = type + 1;
			break;
		case _C_USHT:
			next = type + 1;
			break;
		case _C_INT:
			next = type + 1;
			break;
		case _C_UINT:
			next = type + 1;
			break;
		case _C_LNG:
			next = type + 1;
			break;
		case _C_ULNG:
			next = type + 1;
			break;
		case _C_FLT:
			next = type + 1;
			break;
		case _C_DBL:
			next = type + 1;
			break;
		case _C_BFLD:
			next = type + 1;
			break;
		case _C_VOID:
			next = type + 1;
			break;
		case _C_UNDEF:
			next = type + 1;
			break;
		case _C_PTR:
		{
			CocoaType* n = [[CocoaType typeWithType: type + 1] nextType];
			next = (n ? n->_type : NULL);
			break;
		}	
		case _C_CHARPTR:
			next = type + 1;
			break;
		case _C_ARY_B:
			next = type + 1; // ROB_FIXME
			break;
		case _C_ARY_E:
			next = type + 1;
			break;
		case _C_UNION_B:
			next = type + 1; // ROB_FIXME
			break;
		case _C_UNION_E:
			next = type + 1;
			break;
		case _C_STRUCT_B:
		{
			const char* eq = strchr(type, '=');
			if(eq)
			{
				CocoaType* t = [CocoaType typeWithType: eq+1];
				while(t && (*t->_type != _C_STRUCT_E))
					t = [t nextType];
				t = [t nextType];
				if(t)
					next = t->_type;
			}
			break;
		}
		case _C_STRUCT_E:
			next = type + 1;
			break;
		default:
			next = type + 1;
			break;
	}
	return ((next && *next) ? [CocoaType typeWithType: next] : nil);
}

- (int) byteCount
{
	const char* type = _type;
	if(*type == 'r')
		type++;
	switch(*type)
	{
		case _C_ID:
			return sizeof(id);
		case _C_CLASS:
			return sizeof(Class);
		case _C_SEL:
			return sizeof(SEL);
		case _C_CHR:
			return sizeof(char);
		case _C_UCHR:
			return sizeof(unsigned char);
		case _C_SHT:
			return sizeof(short);
		case _C_USHT:
			return sizeof(unsigned short);
		case _C_INT:
			return sizeof(int);
		case _C_UINT:
			return sizeof(unsigned int);
		case _C_LNG:
			return sizeof(long);
		case _C_ULNG:
			return sizeof(unsigned long);
		case _C_FLT:
			return sizeof(float);
		case _C_DBL:
			return sizeof(double);
		case _C_BFLD:
			return 0;//???
		case _C_VOID:
			return 0;
		case _C_UNDEF:
			return sizeof(void*); //???
		case _C_PTR:
			return sizeof(void*);
		case _C_CHARPTR:
			return sizeof(char*);
		case _C_ARY_B:
			return 0;//ROB_FIXME
		case _C_ARY_E:
			return 0;
		case _C_UNION_B:
			return 0; //ROB_FIXME
		case _C_UNION_E:
			return 0;
		case _C_STRUCT_B:
		{
			const char* eq = strchr(type, '=');
			if(eq)
			{
				int count = 0;
				CocoaType* t = [CocoaType typeWithType: eq+1];
				while(t && (*t->_type != _C_STRUCT_E))
				{
					count += [t byteCount];
					t = [t nextType];
				}
				return count;
			}
			return 0;
		}
		case _C_STRUCT_E:
			return 0;
		default:
			return 0;
	}
}

- (MExpr*) exprForValue: (NSValue*) value
{
	const char* type = _type;
	if(*type == 'r')
		type++;
	switch(*type)
	{
		case _C_ID:
		{
			id obj;
			[value getValue: &obj];
			// don't use uniqueExpr here, it causes infinite recursion for NSDictionary objects
			return (obj ? [obj expr] : kSymbolNull);
		}
		case _C_CLASS:
		{
			Class klass;
			[value getValue: &klass];
			return [[[[CocoaClass alloc] initWithClass: klass] autorelease] 
				performSelector: @selector(uniqueExpr) /* to prevent warning */];
		}
		case _C_SEL:
		{
			SEL sel;
			[value getValue: &sel];
			return MString(NSStringFromSelector(sel));
		}
		case _C_CHR:
		case _C_UCHR:
		{
			char str[2] = { 0 };
			[value getValue: str];
			if(str[0] == YES)
				return MSymbol(@"True");
			else if(str[0] == NO)
				return MSymbol(@"False");
			else if(isprint(str[0]))
				return MString([NSString stringWithCString: str]);
			else
				return MInteger(str[0]);
		}
		case _C_SHT:
		case _C_USHT:
		{
			short s;
			[value getValue: &s];
			return MInteger(s);
		}
		case _C_INT:
		case _C_UINT:
		{
			int i;
			[value getValue: &i];
			return MInteger(i);
		}
		case _C_LNG:
		case _C_ULNG:
		{
			long l;
			[value getValue: &l];
			return MInteger(l);
		}
		case _C_FLT:
		{
			float f;
			[value getValue: &f];
			return MReal(f);
		}
		case _C_DBL:
		{
			double d;
			[value getValue: &d];
			return MReal(d);
		}
		case _C_PTR:
		{
			void* ptr;
			[value getValue: &ptr];
			return MInteger((int) ptr); //ROB_FIXME
		}
		case _C_CHARPTR:
		{
			char* s;
			[value getValue: &s];
			return MString([NSString stringWithCString: s]);
		}
		case _C_ARY_B:
		case _C_ARY_E:
			return nil;
		case _C_UNION_B:
		case _C_UNION_E:
			return nil;
		case _C_STRUCT_B:
		{
			const char* eq = strchr(type, '=');
			if(eq)
			{
				NSMutableArray* memberParts = [NSMutableArray arrayWithCapacity: 0];
				char* data = (char*) malloc([self byteCount]);
				char* ptr = data;
				CocoaType* t = [CocoaType typeWithType: eq+1];
				
				[value getValue: data];
				[memberParts addObject: kSymbolList];
				
				while(t && (*t->_type != _C_STRUCT_E))
				{
					NSValue* member = [NSValue value: ptr withObjCType: t->_type];
					MExpr* e = [member performSelector: @selector(expr)];
					[memberParts addObject: (e ? e : kSymbolNull)];
					ptr += [t byteCount];
					t = [t nextType];
				}
				free(data);
				
				//[structParts addObject: MSymbol(@"CocoaStruct")];
				//[structParts addObject: MString(name)];
				//[structParts addObject: [MExpr exprWithFunction: memberParts]];
				//return [MExpr exprWithFunction: structParts];
				return [MExpr exprWithFunctionParts: memberParts];
			}
			return kSymbolNull;
		}
		case _C_STRUCT_E:
			return nil;
		case _C_VOID:
		case _C_UNDEF:
		default:
			return kSymbolNull;//???
	}
}

- (NSValue*) valueForExpr: (MExpr*) e
{
	const char* type = _type;
	if(*type == 'r')
		type++;
	switch(*type)
	{
		case _C_ID:
		{
			id obj = nil;
			if([e isEqual: kSymbolNull])
				return [NSValue value: &obj withObjCType: type];
			obj = [e cocoaObject];
			if(obj)
			{
				if([obj isKindOfClass: [NSValue class]])
				{
					if(*[obj objCType] == _C_ID)
						return obj;
				}
				if([obj isKindOfClass: [CocoaClass class]])
					obj = [obj klass];
				return [NSValue value: &obj withObjCType: type];
			}
			break;
		}
		case _C_CLASS:
		{
			Class klass;
			if([e checkHead: kSymbolString])
				klass = NSClassFromString([e string]);
			else
			{
				id obj = [e cocoaObject];
				if(obj && [obj isKindOfClass: [CocoaClass class]])
					klass = [obj klass];
			}
			if(klass)
				return [NSValue value: &klass withObjCType: type];
			break;
		}
		case _C_SEL:
		{
			SEL sel = NSSelectorFromString([e string]);
			if(sel == NULL)
				sel = NSSelectorFromString([e symbol]);
			if(sel == NULL)
				sel = NSSelectorFromString([e description]);
			if(sel)
				return [NSValue value: &sel withObjCType: type];
			break;
		}
		case _C_CHR:
		case _C_UCHR:
		{
			if([e checkHead: kSymbolSymbol])
			{
				char c;
				if([e isEqual: kSymbolTrue])
				{
					c = YES;
					return [NSValue value: &c withObjCType: type];
				}
				else if([e isEqual: kSymbolFalse])
				{
					c = NO;
					return [NSValue value: &c withObjCType: type];
				}
				else if([e isEqual: kSymbolNull])
				{
					c = 0;
					return [NSValue value: &c withObjCType: type];
				}
			}
			else if([e checkHead: kSymbolString])
			{
				NSString* str = [e string];
				if([str length] > 0)
				{
					char c = (char) [str characterAtIndex: 0];
					return [NSValue value: &c withObjCType: type];
				}
			}
			else
			{
				NSNumber* val = [e number];
				if(val)
					return [NSNumber numberWithChar: [val charValue]];
			}
			break;
		}
		case _C_SHT:
		case _C_USHT:
		{
			NSNumber* val = [e number];
			if(val)
				return [NSNumber numberWithShort: [val shortValue]];
			break;
		}	
		case _C_INT:
		case _C_UINT:
		{
			NSNumber* val = [e number];
			if(val)
				return [NSNumber numberWithInt: [val intValue]];
			break;
		}
		case _C_LNG:
		case _C_ULNG:
		{
			NSNumber* val = [e number];
			if(val)
				return [NSNumber numberWithLong: [val longValue]];
			break;
		}
		case _C_FLT:
		{
			NSNumber* val = [e number];
			if(val)
				return [NSNumber numberWithFloat: [val floatValue]];
			break;
		}
		case _C_DBL:
		{
			NSNumber* val = [e number];
			if(val)
				return [NSNumber numberWithDouble: [val doubleValue]];
			break;
		}
		case _C_PTR:
		{
			NSNumber* val = [e integer];
			if(val)
				return [NSNumber numberWithLong: [val longValue]];
			// fall through
		}
		case _C_CHARPTR:
		{
			NSString* str = [e string];
			if(str == nil)
				str = [e symbol];
			if(str == nil)
				str = [e description];
			const char* cstr = [str cString];
			if(cstr)
				return [NSValue valueWithPointer: cstr];
			break;
		}
		case _C_BFLD:
		case _C_VOID:
		case _C_UNDEF:
		case _C_ARY_B:
		case _C_ARY_E:
		case _C_UNION_B:
		case _C_UNION_E:
		default:
			break;
		case _C_STRUCT_B:
		{
			int argc;
			if([e checkFunction: kSymbolList length: &argc])
			{
				const char* eq = strchr(type, '=');
				if(eq)
				{
					int byteCount = [self byteCount], arg = 1;
					char* data = (char*) malloc(byteCount);
					char* ptr = data;
					CocoaType* t = [CocoaType typeWithType: eq+1];
					NSValue* value;
//				NSLog(@"t = %@, list = %@", t, e);
					memset(data, 0, byteCount);	
					while(t && (*t->_type != _C_STRUCT_E) && [e functionLength] >= arg)
					{
						value = [t valueForExpr: [e functionPart: arg++]];
//						NSLog(@"subvalue = %@", value);
						if(!value)
						{
							free(data);
							return nil;
						}
						[value getValue: ptr];
						ptr += [t byteCount];
						t = [t nextType];
					}
					value = [NSValue value: data withObjCType: type];
//					NSLog(@"value = %@", value);
					free(data);
					return value;
				}
			}
			break;
		}
		case _C_STRUCT_E:
			break;
	}
	return nil;
}

@end

@implementation NSInvocation (CocoaTypeAdditions)

- (MExpr*) argumentExprAtIndex: (int) index
{
	NSMethodSignature* sig = [self methodSignature];
	if(index >= [sig numberOfArguments])
		return nil;
	
	CocoaType* type = [CocoaType typeWithType: [sig getArgumentTypeAtIndex: index]];
	int len = [type byteCount];
	void* data = malloc(len);
	[self getArgument: data atIndex: index];
	NSValue* value = [NSValue value: data withObjCType: [sig getArgumentTypeAtIndex: index]];
	free(data);
	return [value uniqueExpr];
}

- (BOOL) setArgumentExpr: (MExpr*) expr atIndex: (int) index message: (MExpr**) message
{
	NSMethodSignature* sig = [self methodSignature];
	CocoaType* type = [CocoaType typeWithType: [sig getArgumentTypeAtIndex: index]];
	NSValue* value = [type valueForExpr: expr];
	if(!value)
	{
		if(message)
			*message = MessageExpr(@"CocoaObject", @"conv", [expr description], [type description]);
		return NO;
	}
	
	void* data = malloc([type byteCount]);
	[value getValue: data];
	[self setArgument: data atIndex: index];
	free(data);
	
	return NO;
}

- (MExpr*) returnExpr
{
	NSMethodSignature* sig = [self methodSignature];
	unsigned len = [sig methodReturnLength];
	if(len == 0)
		return kSymbolNull;
	
	void* data = malloc(len);
	[self getReturnValue: data];
	NSValue* value = [NSValue value: data withObjCType: [sig methodReturnType]];
	free(data);
	return [value uniqueExpr];
}

- (BOOL) setReturnExpr: (MExpr*) expr message: (MExpr**) message
{
	NSMethodSignature* sig = [self methodSignature];
	if([sig methodReturnLength] == 0)
		return YES;
	
	CocoaType* type = [CocoaType typeWithType: [sig methodReturnType]];
	NSValue* value = [type valueForExpr: expr];
	if(!value)
	{
		if(message)
			*message = MessageExpr(@"CocoaObject", @"conv", [expr description], [type description]);
		return NO;
	}
	
	void* data = malloc([type byteCount]);
	[value getValue: data];
	[self setReturnValue: data];
	free(data);
	
	return YES;
}

@end
